package inserter

import (
	"context"
	"database/sql"
	errors2 "gitee.com/go-web-2/orm_framework/errors"
	"gitee.com/go-web-2/orm_framework/middleware"
	"gitee.com/go-web-2/orm_framework/predicate"
	"gitee.com/go-web-2/orm_framework/session/db"
	"gitee.com/go-web-2/orm_framework/session/dialect"
	sql2 "gitee.com/go-web-2/orm_framework/sql"
	"gitee.com/go-web-2/orm_framework/sql/common"
)

/*
Inserter: insert into table (column...)
										Values (?,?,?), (?,?,?)
										subQuery
																on duplicate key update column=Values(column)  或 column=?, column2=Values(column2) // mysql
																on conflict (column...) do update set column=excluded.column 或 column=?, column2=excluded.column2 //sqlite
*/

type Inserter[T any] struct {
	columns       []predicate.Column
	values        []*T
	valueSubQuery *predicate.SubQuery
	Updater       *dialect.Updater

	middlewares []middleware.Middleware

	common.SentenceBuilder
	sess db.Session
}

func (i *Inserter[T]) buildValues() error {
	i.Builder.WriteString(" values ")
	for idx, t := range i.values {
		valuer, err := i.ValueCreator(t, i.Model)
		if err != nil {
			return err
		}
		i.Builder.WriteString("(")
		for j, col := range i.columns {
			i.Builder.WriteString("?")
			// 在结构体t中取字段col.ColumnName的值
			var val any
			val, err = valuer.Field(col.ColumnName)
			if err != nil {
				return err
			}
			i.Args = append(i.Args, val)
			if j < len(i.columns)-1 {
				i.Builder.WriteString(",")
			}
		}
		i.Builder.WriteString(")")
		if idx < len(i.values)-1 {
			i.Builder.WriteString(",")
		}
	}
	return nil
}

func (i *Inserter[T]) buildColumns() error {
	i.Builder.WriteString("(")
	for idx, col := range i.columns {
		err := i.BuildColumn(col, sql2.UseAlias{Build: false, Use: false})
		if err != nil {
			return err
		}
		if idx < len(i.columns)-1 {
			i.Builder.WriteString(",")
		}
	}
	i.Builder.WriteString(")")
	return nil
}

func (i *Inserter[T]) buildSubQuery() error {
	query, err := i.valueSubQuery.QueryBuilder.Build()
	if err != nil {
		return err
	}
	i.Builder.WriteString(" (" + query.SQL[:len(query.SQL)-1] + ")")
	i.Args = append(i.Args, query.Args...)
	return nil
}

func (i *Inserter[T]) Build() (*sql2.Query, error) {
	if i.Model == nil {
		var t *T
		i.Model = i.R.Get(t)
	}

	i.Builder.WriteString("insert into " + i.Model.TableName + " ")

	err := i.buildColumns()
	if err != nil {
		return nil, err
	}

	if len(i.values) > 0 {
		err = i.buildValues()
		if err != nil {
			return nil, err
		}
	} else if i.valueSubQuery != nil {
		err = i.buildSubQuery()
		if err != nil {
			return nil, err
		}
	} else {
		return nil, errors2.ErrNotFoundValues
	}

	if i.Updater != nil {
		var updateStat *dialect.UpdateStat
		updateStat, err = i.Dialect.BuildUpdate(i.Updater, i.Model)
		if err != nil {
			return nil, err
		}
		i.Builder.WriteString(updateStat.SQL)
		i.Args = append(i.Args, updateStat.Args...)
	}

	i.Builder.WriteString(";")

	return &sql2.Query{
		SQL:  i.Builder.String(),
		Args: i.Args,
	}, nil
}

func (i *Inserter[T]) ToUpdateBuilder() *UpdateBuilder[T] {
	return &UpdateBuilder[T]{
		Inserter: i,
	}
}

func (i *Inserter[T]) Columns(columns ...predicate.Column) *Inserter[T] {
	i.columns = columns
	return i
}

func (i *Inserter[T]) Values(value ...*T) *Inserter[T] {
	i.values = value
	return i
}

func (i *Inserter[T]) ValueSubQuery(query *predicate.SubQuery) *Inserter[T] {
	i.valueSubQuery = query
	return i
}

func (i *Inserter[T]) Exec(ctx context.Context) *sql2.QueryResult {
	query, err := i.Build()
	if err != nil {
		return &sql2.QueryResult{
			Err: err,
		}
	}

	var root middleware.Handler = func(c context.Context, qc *sql2.QueryContext) *sql2.QueryResult {
		var res sql.Result
		res, err = i.sess.ExecContext(ctx, query.SQL, query.Args...)
		if err != nil {
			return &sql2.QueryResult{
				Err: err,
			}
		}
		return &sql2.QueryResult{
			Res: res,
		}
	}

	for _, m := range i.middlewares {
		root = m(root)
	}

	return root(ctx, &sql2.QueryContext{
		Model:   i.Model,
		Builder: i,
		Type:    "SELECT",
	})
}

func NewInserter[T any](sess db.Session, middlewares ...middleware.Middleware) *Inserter[T] {
	return &Inserter[T]{
		sess:        sess,
		middlewares: middlewares,
		SentenceBuilder: common.SentenceBuilder{
			Core: sess.GetCore(),
		},
	}
}

var _ sql2.Executor[any] = (*Inserter[any])(nil)
